﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Buddy.BehaviorTree;
using Buddy.Common;
using Buddy.Common.Math;
using Buddy.CommonBot;
using Buddy.Navigation;
using Buddy.Swtor;
using Buddy.Swtor.Objects;

using WingIt.Routines;
using WingIt.Routines.Mirrors;

using Action = Buddy.BehaviorTree.Action;


namespace WingIt
{
    [RevisionControlId("$Id: BuffAndRevive.cs 701 2012-09-04 19:14:29Z Chinajade $")]
    public static partial class WingItCommon
    {
        /// <summary>
        /// If DOWHEN evaluates true (or is omitted), will periodically offer our "self buff" to non-group members with which we cross paths.
        /// </summary>
        /// <param name="doWhen"></param>
        /// <returns></returns>
        ///  1Aug2012-23:17UTC chinajade
        public static Composite     DriveByLove(CanRunDecoratorDelegate doWhen = null)
        {
            TimeSpan    DriveByThrottleFull  = TimeSpan.FromSeconds(120);
            TimeSpan    DriveByThrottleRetry = TimeSpan.FromSeconds(3);
            float       nearbyRange = Global.Distance.Ranged * 0.85f;

            doWhen = doWhen ?? (c => true);     // Resolve null argument into something sane

            return new Decorator(whenThrottleAllows => doWhen(whenThrottleAllows) && driveByThrottle.IsReady(),
                new Sequence(player => Me.PlayersAround_Friendly(nearbyRange).FirstOrDefault(p => p.InLineOfSight && (p != Me)
                                                                                                && !p.IsInMyParty() && !p.HasBuff(Me.SelfBuffName())),
                    new Action(player => {
                        // If no players found, we're done...
                        // We will check again after the 'retry time' has elapsed--since we don't want to continuously
                        // look for a player to buff at each BT pulse.
                        if (player == null)
                        {
                            driveByThrottle.Used(DriveByThrottleRetry);
                            return RunStatus.Failure;
                        }

                        // Record player we found to buff, and apply full-duration throttle...
                        // The full-duration throttle prevents us from getting gummed up buffing people in high traffic areas, such as Fleet.
                        Logger.Write("Drive-by buffing '{0}' on: {1}", Me.SelfBuffName(), ((TorPlayer)player).LoggableIdentifier());
                        driveByThrottle.Used(DriveByThrottleFull);
                        return RunStatus.Success; // fall through
                        }),
                    Mirror.Cast(Me.SelfBuffName(), player => (TorPlayer)player)
                    ));
        }
        private static Throttle driveByThrottle = new Throttle_Timed("DriveByLove");

        /// <summary>
        /// <para>If DOWHEN evaluates to true, revives a dead party members and their companion.  This behavior should eventually summon our companion also,
        /// when the BW API supports it.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If DOWHEN is omitted, the behavior will always be attempted.</para></description></item>
        /// <item><description><para> * The behavior moves the toon to the dead player's or companion's body, if needed.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns><para>a behavior tree Composite for use in a PrioritySelector.</para>
        /// <para>The Composite returns RunStatus.Success to the BT parent if the behavior is in progress.
        /// Otherwise, returns RunStatus.Failure indicating no action was necessary.</para>
        /// </returns>
        /// 15Jul2012-03:02UTC chinajade
        public static Composite     ReviveAndRebuffPartyMembers_OutOfCombat(CanRunDecoratorDelegate doWhen = null)
        {
            TimeSpan    CheckPartyThrottleDuration  = TimeSpan.FromMilliseconds(1250);
            string      IntentNotifyTokenPrefix     = Logger.GetMyMethodName() + ".";
            TimeSpan    PlayerBlacklistDuration     = TimeSpan.FromSeconds(90);

            doWhen = doWhen ?? (ctx => true);       // Resolve optional argument to something sane

            return new Decorator(whenMovementEnabled => LazyRaider.IsMovementEnabled && doWhen(whenMovementEnabled) && checkPartyThrottle.IsReady(),
                    new PrioritySelector(
                        // First, get dead players back on their feet...
                        new Decorator(whenDeadPartyPlayers => Me.PartyPlayers(p => p.IsDead && !LocalBlacklist.IsBlacklisted(p.Guid, LocalBlacklist.Purpose.Resurrect)).Count() > 0,
                            new PrioritySelector(deadPlayer => Me.PartyPlayers(p => p.IsDead && !LocalBlacklist.IsBlacklisted(p.Guid, LocalBlacklist.Purpose.Resurrect)).OrderBy(p => p.Distance).FirstOrDefault(),
                                new Decorator(deadPlayer => Poi.Current.Type != PoiType.None,
                                    new Action(deadPlayer => {
                                        Poi.Clear("Reviving party member: " + ((TorPlayer)deadPlayer).LoggableIdentifier());
                                        return (RunStatus.Failure); // fall through
                                        })),
                                // CreateBTPS.IntentNotify(deadPlayer => IntentNotifyTokenPrefix + ((TorPlayer)deadPlayer).Name,
                                //                         deadPlayer => "Reviving " + ((TorPlayer)deadPlayer).LoggableIdentifier(),
                                //                         PlayerBlacklistDuration),
                                CreateBTPS.BreakCrouchOrCover(),    // in case player died on top of us, and no movement required
                                CreateBTPS.MoveWithinRangeAndLoS(deadPlayer => ((TorPlayer)deadPlayer), 0.2f),
                                CreateBTPS.StopWithinRangeAndLoS(deadPlayer => ((TorPlayer)deadPlayer), 0.2f),
                                new Sequence(
                                    Mirror.Cast("Revive", deadPlayer => ((TorPlayer)deadPlayer)),
                                    new Action(deadPlayer => {
                                        LocalBlacklist.Blacklist(((TorPlayer)deadPlayer).Guid, LocalBlacklist.Purpose.Resurrect, PlayerBlacklistDuration);
                                        }),
                                    CreateBTPS.IntentComplete(deadPlayer => IntentNotifyTokenPrefix + ((TorPlayer)deadPlayer).Name)
                                    )
                                )),

                        // Second, get player's companions back on their feet...
                        new Decorator(whenDeadPartyCompanions => Me.PartyCompanions(c => c.IsDead && c.Distance < (2 * Global.Distance.Ranged)).Count() > 0,
                            new PrioritySelector(deadCompanion => Me.PartyCompanions(c => c.IsDead).OrderBy(c => c.Distance).FirstOrDefault(),
                                new Decorator(deadCompanion => Poi.Current.Type != PoiType.None,
                                    new Action(deadCompanion => {
                                        Poi.Clear("Reviving party companion:" + ((TorCharacter)deadCompanion).LoggableIdentifier());
                                        return (RunStatus.Failure); // fall through
                                        })),
                                // CreateBTPS.IntentNotify(deadCompanion => IntentNotifyTokenPrefix + ((TorCharacter)deadCompanion).Name,
                                //                         deadCompanion => "Reviving " + ((TorCharacter)deadCompanion).LoggableIdentifier()),
                                CreateBTPS.BreakCrouchOrCover(),    // in case companion died on top of us, and no movement required
                                CreateBTPS.MoveWithinRangeAndLoS(deadCompanion => ((TorCharacter)deadCompanion), 0.2f),
                                CreateBTPS.StopWithinRangeAndLoS(deadCompanion => ((TorCharacter)deadCompanion), 0.2f),
                                new Sequence(
                                    Mirror.Cast("Revive Companion", deadCompanion => ((TorCharacter)deadCompanion)),
                                    CreateBTPS.IntentComplete(deadCompanion => IntentNotifyTokenPrefix + ((TorCharacter)deadCompanion).Name)
                                    )
                                )),

                        // TODO: Worry about summon (of my companion) when the BuddyWing API supports it...
                        //new Decorator(whenCompanionNotHere => Me.CompanionSummonable, //Summon)

                        // Rebuff party...
                        new Decorator(whenUnbuffedPartyMembers => Me.PartyPlayers(p => p.IsInRange(Global.Distance.Ranged * 0.75f) && !p.HasBuff(Me.SelfBuffName())).Count() > 0,
                            new Sequence(players => Me.PartyPlayers(p => p.IsInRange(Global.Distance.Ranged * 0.75f) && !p.HasBuff(Me.SelfBuffName())),
                                new Action(players => {
                                    Logger.Write("Buffing party players missing '{0}': {1}", Me.SelfBuffName(), string.Join(", ", ((IEnumerable<TorPlayer>)players).Select(p => p.LoggableIdentifier())));
                                    }),
                                // NB: We can't use Mirror.BuffSelf() here, because it won't apply buff if *we* have it.
                                // We need to blindly apply the self buff so other party members will get our missing buff. 
                                Mirror.Cast(Me.SelfBuffName(), players => ((IEnumerable<TorPlayer>)players).FirstOrDefault())
                                )),

                        // Everyone up & buffed, wait a while before we look again...
                        new Action(throttlePartyCheck => checkPartyThrottle.Used(CheckPartyThrottleDuration))
                        ));
        }
        private static Throttle checkPartyThrottle = new Throttle_Timed("CheckParty");

        /// <summary>
        /// <para>If DOWHEN evaluates to true, uses the class-specific ability to restore health and resources to the toon and companion.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If DOWHEN is omitted, the behavior will always be attempted.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns><para>a behavior tree Composite for use in a PrioritySelector.</para>
        /// <para>The Composite returns RunStatus.Success to the BT parent if the behavior is in progress.
        /// Otherwise, returns RunStatus.Failure indicating no action was necessary.</para>
        /// </returns>
        ///  7Jul2012-23:23UTC chinajade
        public static Composite ToonAndCompanionRejuvenate(CanRunDecoratorDelegate doWhen = null)
        {
            doWhen = doWhen ?? (ctx => true);       // Resolve optional argument to something sane

            return new Decorator(whenRejuvenationNeeded => doWhen(whenRejuvenationNeeded) && IsMeOrCompanionRejuvenationNeeded(),
                    new PrioritySelector(ability => Me.GetMirrorAbility(Me.RejuvenateAbilityName()),
                        CreateBTPS.BreakCrouchOrCover(),
                        CreateBTPS.StopWithinRangeAndLoS(target => BuddyTor.Me, float.MaxValue),    // just stop moving

                        new Decorator(ability => !Me.HasBuff(Me.RejuvenateAbilityName()),
                            new Sequence(
                                // We want to terminate the channeling when it is no longer needed, so we need to do this ourselves
                                // instead of depending on a Mirror.Cast() variant (that will wait full-term for channeling to stop).
                                WingIt.Instance.Cast(Me.RejuvenateAbilityName(), rejuvenateAbilityOn => Me, rejuvenateAbilityWhen => true),
                                Mirror.WaitForCastingStart(ability => (TorAbility)ability),
                                Mirror.WaitForCastingComplete(ability => (TorAbility)ability, stopWhen => IsMeAndCompanionRejuvenationComplete()),
                                CreateBTPS.StopCasting()
                                ))
                        ));
        }

        /// <summary>
        /// <para>If DOWHEN evaluates to true, buffs the toon and companion with class-specific, common buffs.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If DOWHEN is omitted, the behavior will always be attempted.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns><para>a behavior tree Composite for use in a PrioritySelector.</para>
        /// <para>The Composite returns RunStatus.Success to the BT parent if the behavior is in progress.
        /// Otherwise, returns RunStatus.Failure indicating no action was necessary.</para>
        /// </returns>
        ///  7Jul2012-23:23UTC chinajade
        public static Composite ToonSelfBuff(CanRunDecoratorDelegate doWhen = null)
        {
            doWhen = doWhen ?? (ctx => true);       // Resolve optional argument to something sane

            return new Decorator(whenSelfBuffAllowed => doWhen(whenSelfBuffAllowed),
                new PrioritySelector(
                // NB: Currently, these buffs must be insta-cast, as the toon may be moving when they're cast...
                // If you need to violate this, you'll need to include a CommonBehaviors.MoveStop()
                // in your action, and use WaitContinue() for casting to complete.
                Mirror.BuffSelf("Sprint"),
                Mirror.BuffSelf(Me.SelfBuffName())
                ));
        }

        private static bool IsMeAndCompanionRejuvenationComplete()
        {
            return (Me.IsRejuvenationComplete() && Me.IsCompanionRejuvenationComplete());
        }

        private static bool IsMeOrCompanionRejuvenationNeeded()
        {
            return (Me.IsRejuvenationNeeded() || Me.IsCompanionRejuvenationNeeded());
        }
    }
}
